#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using AbstractClass.Collections.Generic;

namespace AbstractClass.Runtime.Serialization
{
    /// <summary>
    /// Represents a serialization surrogate for non-serializable class.
    /// </summary>
    public sealed class NonSerialiazableTypeSurrogateSelector : ISerializationSurrogate, ISurrogateSelector
    {
        private readonly HashMap<Type, ISerializationSurrogate> _handles = new HashMap<Type, ISerializationSurrogate>();
        private ISurrogateSelector _nextSelector;

        #region ISerializationSurrogate Members

        void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            var dictionary = new Dictionary<string, object>();
            IEnumerable<FieldInfo> fieldInfos = ReturnAllFields(obj.GetType());
            foreach (FieldInfo fi in fieldInfos)
            {
                string key = ExtractNameFromFieldInfo(fi);
                object value = fi.GetValue(obj);

                if (dictionary.Keys.Contains(key)) continue;

                if (IsKnownType(fi.FieldType))
                {
                    info.AddValue(key, value);
                }
                else if (fi.FieldType.IsClass)
                {
                    info.AddValue(key, value);
                }
                dictionary.Add(key, value);
            }
        }

        object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context,
                                                     ISurrogateSelector selector)
        {
            IEnumerable<FieldInfo> fieldInfos = ReturnAllFields(obj.GetType());
            foreach (FieldInfo fi in fieldInfos)
            {
                if (IsKnownType(fi.FieldType))
                {
                    if (IsNullableType(fi.FieldType))
                    {
                        Type argumentValueForTheNullableType = GetFirstArgumentOfGenericType(fi.FieldType);
                        fi.SetValue(obj, info.GetValue(ExtractNameFromFieldInfo(fi), argumentValueForTheNullableType));
                    }
                    else
                    {
                        fi.SetValue(obj, info.GetValue(ExtractNameFromFieldInfo(fi), fi.FieldType));
                    }
                }
                else if (fi.FieldType.IsClass)
                {
                    fi.SetValue(obj, info.GetValue(ExtractNameFromFieldInfo(fi), fi.FieldType));
                }
            }

            return obj;
        }

        #endregion

        #region ISurrogateSelector Members

        void ISurrogateSelector.ChainSelector(ISurrogateSelector selector)
        {
            _nextSelector = selector;
        }

        ISerializationSurrogate ISurrogateSelector.GetSurrogate(Type type, StreamingContext context,
                                                                out ISurrogateSelector selector)
        {
            selector = this;

            if (IsKnownType(type))
            {
                return null;
            }
            if (type.IsClass || type.IsValueType)
            {
                if (!_handles.Keys.Contains(type))
                {
                    _handles.Add(type,
                                 FormatterServices.GetSurrogateForCyclicalReference(
                                     new NonSerialiazableTypeSurrogateSelector()));
                }
                return _handles[type];
            }
            return null;
        }

        ISurrogateSelector ISurrogateSelector.GetNextSelector()
        {
            return _nextSelector;
        }

        #endregion

        private static string ExtractNameFromFieldInfo(MemberInfo fi)
        {
            return fi.DeclaringType.FullName + "^" + fi.Name + fi.MetadataToken;
        }

        private static IEnumerable<FieldInfo> ReturnAllFields(Type obj)
        {
            var collect = new List<FieldInfo>();
            if (obj.BaseType != typeof (object))
            {
                collect.AddRange(ReturnAllFields(obj.BaseType));
            }
            return
                obj.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic |
                              BindingFlags.FlattenHierarchy).Union(collect).ToArray();
        }

        private static bool IsKnownType(Type type)
        {
            return type == typeof (string)
                   || type.IsPrimitive;
        }

        private static Type GetFirstArgumentOfGenericType(Type type)
        {
            return type.GetGenericArguments()[0];
        }

        private static bool IsNullableType(Type type)
        {
            if (type.IsGenericType)
                return type.GetGenericTypeDefinition() == typeof (Nullable<>);
            return false;
        }
    }
}